# This program is free software; you can redistribute it and/or modify
# it under the terms of the (LGPL) GNU Lesser General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library Lesser General Public License for more details at
# ( http://www.gnu.org/licenses/lgpl.html ).
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# written by: Jeff Ortel ( jortel@redhat.com )

"""
Provides classes for handling soap multirefs.
"""

from logging import getLogger
from string import lower
from urllib import unquote

from suds import *
from suds.sax.element import Element

log = getLogger(__name__)

soapenc = (None, 'http://schemas.xmlsoap.org/soap/encoding/')
xop = ('xop', 'http://www.w3.org/2004/08/xop/include')


class Visitor:

    def __init__(self, context):
        self.context = context

    def visit(self, node):
        raise NotImplementedError


class Visitors(Visitor):

    def __init__(self, context, visitors=[]):
        Visitor.__init__(self, context)
        self.visitors = list(visitors)

    def visit(self, node):
        for visitor in self.visitors:
            visitor.visit(node)

    def __call__(self, document):
        self.visit(document)
        for node in document.children:
            self(node)
        return document


class FilterSoapRoot:

    def __init__(self, context):
        self.context = context

    def __call__(self, document):
        """Filter out to keep only soap encoded root.
        """
        nodes = []
        for child in document.children:
            if self.is_soap_root(child):
                nodes.append(child)

        document.children = nodes
        return document

    def is_soap_root(self, node):
        """
        Get whether the specified I{node} is a soap encoded root.
        This is determined by examining @soapenc:root='1'.
        The node is considered to be a root when the attribute
        is not specified.
        @param node: A node to evaluate.
        @type node: L{Element}
        @return: True if a soap encoded root.
        @rtype: bool
        """
        root = node.getAttribute('root', ns=soapenc)
        if root is None:
            return True
        return root.value == '1'


class MultiRefSupport(Visitor):
    """
    Resolves and replaces multirefs.
    @ivar nodes: A list of non-multiref nodes.
    @type nodes: list
    @ivar catalog: A dictionary of multiref nodes by id.
    @type catalog: dict
    """

    def __init__(self, context):
        Visitor.__init__(self, context)
        self.catalog = {}

    def __call__(self, document):
        """
        Create the I{catalog} of multiref nodes by id and the list of
        non-multiref nodes.
        @param document: A soap envelope document node.
        @type document: L{Element}
        """
        self.catalog = {}
        for child in document.children:
            id = child.get('id')
            if id is None:
                continue
            key = '#%s' % id
            self.catalog[key] = child
        return document

    def visit(self, node):
        """
        Replacing the I{multiref} references with the contents of the
        referenced nodes and remove the I{href} attribute.  Warning:  since
        the I{ref} is not cloned,
        @param node: A node to update.
        @type node: L{Element}
        """
        href = node.getAttribute('href')
        if href is None:
            return
        id = href.getValue()
        if (not len(id)) or (id[0] != '#'):
            # references always starts with # We have to pay attention
            # not to consfuse these with multipart references, added
            # with include.
            return
        ref = self.catalog.get(id)
        if ref is None:
            log.error('soap multiref: %s, not-resolved', id)
            return
        node.append(ref.children)
        node.setText(ref.getText())
        for a in ref.attributes:
            if a.name != 'id':
                node.append(a)
        node.remove(href)


class XOpSupport(Visitor):

    def get_part(self, content_id):
        # There are two possible format for content id in multipart.
        content_ids = ('<%s>' % content_id, content_id)
        for part in self.context.parts:
            if part['content-id'] in content_ids:
                return part
        return None

    def visit(self, node):
        if node.namespace() != xop:
            return
        if lower(node.name) == 'include':
            href = node.getAttribute('href')
            if href is None:
                log.error('xop error: missing uri')
                return
            uri = unquote(href.value)
            if not uri.startswith('cid:'):
                log.error('xop error: uri %s, not-resolvable', uri)
                return
            part = self.get_part(uri[4:])
            if part is None:
                log.error('xop error: uri %s is un-resolved', uri)
            else:
                node.parent.annotations['data'] = part.get_payload(decode=True)
                node.detach()
        else:
            log.error('xop: unknow action %s', lower(node.name))


def xml_transform(context, document):
    filter_root = FilterSoapRoot(context)
    multi_ref = MultiRefSupport(context)
    visitors = Visitors(context, [XOpSupport(context), multi_ref])

    for processor in [filter_root, multi_ref, visitors]:
        document = processor(document)
    return document
